#ifndef VECTOR_HH_
# define VECTOR_HH_
# include "Geom.hh"
# include <cmath>
#include <QtXml>

# define ASSIGN_PLUS_MINUS(OP)                          \
    public: Vector& operator OP (Vector const& rval) {  \
        x OP rval.x;                                    \
        y OP rval.y;                                    \
        return *this;                                   \
    }                                                   \

# define ASSIGN_MUL_DIV(OP)                 \
    public: Vector& operator OP (T rval) {  \
        x OP rval;                          \
        y OP rval;                          \
        return *this;                       \
    }                                       \


template< class T > class Geom::Vector {
    public: T x;
    public: T y;

    public: explicit Vector(T x = T(), T y = T()):
        x(x),
        y(y) {}

    public: template< class U > Vector(Vector< U > const& copy):
        x(copy.x),
        y(copy.y) {}

    public: operator bool () const {
        return *this != Vector();
    }

    public: template< class U > operator Vector< U > () const {
        return Vector< U >(static_cast< U >(x), static_cast< U >(y));
    }

    public: bool operator ! () const {
        return !static_cast< bool >(*this);
    }

    public: Vector operator + () const {
        return *this;
    }

    public: Vector operator - () const {
        return Vector(-x, -y);
    }

    ASSIGN_PLUS_MINUS(=)
    ASSIGN_PLUS_MINUS(+=)
    ASSIGN_PLUS_MINUS(-=)
    ASSIGN_MUL_DIV(*=)
    ASSIGN_MUL_DIV(/=)

    public: T get_length() const {
        return sqrt(x * x + y * y);
    }

    public: void set_length(T length) {
        T angle = get_angle();
        x = length * cos(angle);
        y = length * sin(angle);
    }

    public: T get_angle() const {
        return atan2(y, x);
    }

    public: void set_angle(T angle) {
        T length = get_length;
        x = length * cos(angle);
        y = length * sin(angle);
    }

    protected: struct NumParseError {
        void operator () () {
            throw *this;
        }
    };

    public: bool from_xml(QDomElement const& vectorElem) {
        QDomElement const xElem = vectorElem.firstChildElement("x");
        QDomElement const yElem = vectorElem.firstChildElement("y");

        try {
            T const _x = parse_num< T, NumParseError >(xElem);
            T const _y = parse_num< T, NumParseError >(yElem);
            x = _x;
            y = _y;
            return true;
        } catch (NumParseError& ex) {
            return false;
        }
    }

    public: bool to_xml(QDomElement& parent, QString const& name) const {
        QDomDocument doc = parent.ownerDocument();
        QDomElement vectorElem = doc.createElement(name);
        parent.appendChild(vectorElem);
        ::to_xml(vectorElem, "x", x);
        ::to_xml(vectorElem, "y", y);
        return !vectorElem.isNull();
    }

    friend Vector operator + (Vector const& lval, Vector const& rval) {
        return Vector(lval.x + rval.x, lval.y + rval.y);
    }

    friend Vector operator - (Vector const& lval, Vector const& rval) {
        return lval + (-rval);
    }

    friend T operator * (Vector const& lval, Vector const& rval) {
        return lval.x * rval.x + lval.y * rval.y;
    }

    friend Vector operator * (T lval, Vector const& rval) {
        return Vector(lval * rval.x, lval * rval.y);
    }

    friend Vector operator * (Vector const& lval, T rval) {
        return Vector(lval.x * rval, lval.y * rval);
    }

    friend Vector operator / (Vector const& lval, T rval) {
        return lval * (1 / rval);
    }

    friend bool operator == (Vector const& lval, Vector const& rval) {
        return lval.x == rval.x && lval.y == rval.y;
    }

    friend bool operator != (Vector const& lval, Vector const& rval) {
        return !(lval == rval);
    }

    friend bool operator < (Vector const& lval, Vector const& rval) {
        if (lval.x < rval.x) {
           return true;
        } else if (lval.x > rval.x) {
            return false;
        } else {
            return lval.y < rval.y;
        }
    }

    friend bool operator > (Vector const& lval, Vector const& rval) {
        if (lval.x > rval.x) {
           return true;
        } else if (lval.x < rval.x) {
            return false;
        } else {
            return lval.y > rval.y;
        }
    }

    friend bool operator <= (Vector const& lval, Vector const& rval) {
        return !(lval > rval);
    }

    friend bool operator >= (Vector const& lval, Vector const& rval) {
        return !(lval < rval);
    }
};

#undef ASSIGN_PLUS_MINUS
#undef ASSIGN_MUL_DIV

#endif // ndef VECTOR_HH_
